package com.demo.config.redis;

import com.alibaba.fastjson.JSON;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * redis配置项
 */
@Slf4j
@Component
public class RedisUtil {

	@Resource
	private RedisTemplate redisTemplate;

	private static Map<String, LockInfo> lockInfoMap = new ConcurrentHashMap<>();

	private static final Long SUCCESS = 1L;

	// ------------------websock------------------------

	// 2.添加静态的变量
	public static RedisTemplate redis;

	@PostConstruct
	public void getRedisTemplate() {
		redis = this.redisTemplate;
	}
	// ------------------websock------------------------

	/**
	 * 加锁内置自定义字段
	 */
	@Data
	public static class LockInfo {

		private String key;

		private String value;

		private int expireTime;

		// 更新时间
		private long renewalTime;

		// 更新间隔
		private long renewalInterval;

		public static LockInfo getLockInfo(String key, String value, int expireTime) {
			LockInfo lockInfo = new LockInfo();
			lockInfo.setKey(key);
			lockInfo.setValue(value);
			lockInfo.setExpireTime(expireTime);
			lockInfo.setRenewalTime(System.currentTimeMillis());
			lockInfo.setRenewalInterval(expireTime * 2000 / 3);
			return lockInfo;
		}

	}

	/**
	 * 使用lua脚本更新redis锁的过期时间
	 * @param lockKey
	 * @param value
	 * @return 成功返回true, 失败返回false
	 */
	public boolean renewal(String lockKey, String value, int expireTime) {
		String luaScript = """
				if
				    redis.call('get', KEYS[1]) == ARGV[1]
				then
				    return redis.call('expire', KEYS[1], ARGV[2])
				else
				    return 0
				end";
				""";
		DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>();
		redisScript.setResultType(Boolean.class);
		redisScript.setScriptText(luaScript);
		List<String> keys = new ArrayList<>();
		keys.add(lockKey);

		Object result = redisTemplate.execute(redisScript, keys, value, expireTime);
		log.info("更新redis锁的过期时间：{}", result);
		return (boolean) result;
	}

	/**
	 * 加锁
	 * @param lockKey 锁
	 * @param value 身份标识（保证锁不会被其他人释放）
	 * @param expireTime 锁的过期时间（单位：秒）
	 * @return 成功返回true, 失败返回false
	 */
	public boolean lock(String lockKey, String value, long expireTime) {
		return redisTemplate.opsForValue().setIfAbsent(lockKey, value, expireTime, TimeUnit.SECONDS);
	}

	/**
	 * redisTemplate解锁
	 * @param key
	 * @param value
	 * @return 成功返回true, 失败返回false
	 */
	public boolean unlock(String key, String value) {
		Object currentValue = redisTemplate.opsForValue().get(key);
		boolean result = false;
		if (!Optional.ofNullable(currentValue).isPresent() && value.equals(currentValue)) {
			result = redisTemplate.opsForValue().getOperations().delete(key);
		}
		return result;
	}

	/**
	 * 定时去检查redis锁的过期时间
	 */
	@Scheduled(fixedRate = 5000L)
	@Async("redisExecutor")
	public void renewal() {
		long now = System.currentTimeMillis();
		for (Map.Entry<String, LockInfo> lockInfoEntry : lockInfoMap.entrySet()) {
			LockInfo lockInfo = lockInfoEntry.getValue();
			if (lockInfo.getRenewalTime() + lockInfo.getRenewalInterval() < now) {
				renewal(lockInfo.getKey(), lockInfo.getValue(), lockInfo.getExpireTime());
				lockInfo.setRenewalTime(now);
				log.info("lockInfo {}", JSON.toJSONString(lockInfo));
			}
		}
	}

}
